package pulpfizz.pulp.body;

import org.jbox2d.collision.AABB;
import org.jbox2d.collision.CircleShape;
import org.jbox2d.collision.PolygonShape;
import org.jbox2d.collision.Shape;
import org.jbox2d.common.Vec2;
import org.jbox2d.dynamics.Body;
import pulpcore.animation.BindFunction;
import pulpcore.math.CoreMath;
import pulpcore.sprite.Sprite;

import java.util.ArrayList;
import java.util.Arrays;

/**
 * This class holds a Body object's dimensions and synchronizes its coordinates.
 */
public abstract class BodySpriteOld extends Sprite
{

	public Body getBody()
	{
		return body;
	}

	protected Body body;
	protected PhysicsLayer physics;
	protected float pScale;

	protected float offsetX;
	protected float offsetY;

	/**
	 * The padding field stores how much "padding" exists between the maximum
	 * boundary of the Body and the bounding box of this BodySprite.
	 */
	private float padding;

	private BodySpriteOld(PhysicsLayer physics)
	{
		super(0, 0, 0, 0);
		this.physics = physics;
		this.pScale = physics.getScaling();
	}

	public BodySpriteOld(Body body, PhysicsLayer physics)
	{
		this(physics);
		setBody(body);
		this.setAnchor(Sprite.CENTER);
	}
	
	public BodySpriteOld(Body body, PhysicsLayer physics, AABB aabb) {
		this(physics);
		setBody(body, aabb);
		this.setAnchor(Sprite.CENTER);
	}
	
	public void setBody(Body b, AABB aabb) {
		this.body = b;
		setBodyDimensions(aabb);
		this.x.bindTo(posX(body));
		this.y.bindTo(posY(body));
		this.angle.bindTo(getAngle(body));
	}

	public void setBody(Body b)
	{
		this.body = b;
		setBodyDimensions();
		this.x.bindTo(posX(body));
		this.y.bindTo(posY(body));
		this.angle.bindTo(getAngle(body));
	}

	/*
	* Extracts the Body's Axis Aligned Bounding Box from its Shape list,
	* and sets the sprite's size and coordinate offset to the extracted values.
	*/
	public void setBodyDimensions()
	{
		Shape s = body.getShapeList();
		ArrayList<Vec2> vec = new ArrayList<Vec2>();
		while (s != null)
		{
			switch (s.getType())
			{
				case CIRCLE_SHAPE:
					CircleShape c = (CircleShape) s;
					Vec2 pos = c.getLocalPosition();
					vec.add(new Vec2(pos.x - c.getRadius(), pos.y - c.getRadius()));
					vec.add(new Vec2(pos.x + c.getRadius(), pos.y + c.getRadius()));
					break;
				case POLYGON_SHAPE:
					PolygonShape p = (PolygonShape) s;
					Vec2[] v = p.getVertices();
					vec.addAll(Arrays.asList(v));
					break;
			}
			s = s.getNext();
		}
		float hiX = CoreMath.MIN_FLOAT_VALUE, hiY = CoreMath.MIN_FLOAT_VALUE, loX = CoreMath.MAX_FLOAT_VALUE, loY = CoreMath.MAX_FLOAT_VALUE;
		for (Vec2 vec2 : vec)
		{
			loX = Math.min(vec2.x, loX);
			hiX = Math.max(vec2.x, hiX);
			loY = Math.min(vec2.y, loY);
			hiY = Math.max(vec2.y, hiY);
		}

		loX -= padding / pScale;
		loY -= padding / pScale;
		hiX += padding / pScale;
		hiY += padding / pScale;

		float width = hiX - loX;
		float height = hiY - loY;
		this.setSize(width * pScale, height * pScale);

		/**
		 * This coordinate offset is used to translate the physics sprite's
		 * position offset from the center.
		 */
		offsetX = (loX + width / 2) * pScale;
		offsetY = -(loY + height / 2) * pScale;
	}
	
	public void setBodyDimensions(AABB aabb) {
		float loX = aabb.lowerBound.x - padding / pScale;
		float loY = aabb.lowerBound.y - padding / pScale;
		float hiX = aabb.upperBound.x + padding / pScale;
		float hiY = aabb.upperBound.y + padding / pScale;

		float width = hiX - loX;
		float height = hiY - loY;
		this.setSize(width * pScale, height * pScale);

		/**
		 * This coordinate offset is used to translate the physics sprite's
		 * position offset from the center.
		 */
		offsetX = (loX + width / 2) * pScale;
		offsetY = -(loY + height / 2) * pScale;
	}

	/**
	 * Updates the location of the BodySprite based on the Body coordinates. We
	 * need to do a bit of trig in order to shift the Body to the extracted
	 * offset based on its current angle.
	 */
	public BindFunction posX(final Body body)
	{
		return new BindFunction()
		{
			public Number f()
			{
				double f = physics.getCanvasX(body);
				double angle = physics.getCanvasAngle(body);
				double xCos = offsetX * CoreMath.toDouble(CoreMath.cos(CoreMath.toFixed(angle)));
				double ySin = offsetY * CoreMath.toDouble(CoreMath.sin(CoreMath.toFixed(angle)));
				return f + xCos - ySin;
			}
		};
	}

	public BindFunction posY(final Body body)
	{
		return new BindFunction()
		{
			public Number f()
			{
				double f = physics.getCanvasY(body);
				double angle = physics.getCanvasAngle(body);
				double xSin = offsetX * CoreMath.toDouble(CoreMath.sin(CoreMath.toFixed(angle)));
				double yCos = offsetY * CoreMath.toDouble(CoreMath.cos(CoreMath.toFixed(angle)));
				return f + xSin + yCos;
			}
		};
	}

	BindFunction getAngle(final Body body)
	{
		return new BindFunction()
		{
			public Number f()
			{
				return (physics.getCanvasAngle(body));
			}
		};
	}

	public float getPadding()
	{
		return padding;
	}

	public void setPadding(float padding)
	{
		this.padding = padding;
		// Make sure to re-calculate body dimensions here, so we can set ourselves to the right size with the new padding.
		setBodyDimensions();
	}
	
	/*
	 * Just a simple way of having a BoydSprite destroy itself.
	 */
	public void destroy()
	{
		physics.remove(this);
	}
}
